package net.towee.client.com.dashboard.presenter;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

import net.towee.client.ToweeGinjector;
import net.towee.client.com.dashboard.BoardEditor;
import net.towee.client.com.dashboard.DashBoard;
import net.towee.client.com.dashboard.GadgetPusher;
import net.towee.client.event.core.BoardCreatedEvent;
import net.towee.client.event.core.BoardCreatedHandler;
import net.towee.client.event.core.BoardRemovedEvent;
import net.towee.client.event.core.BoardRemovedHandler;
import net.towee.client.event.core.GroupDetachedFromBoardEvent;
import net.towee.client.event.core.GroupDetachedFromBoardHandler;
import net.towee.client.gadgets.GadgetPresenter;
import net.towee.client.gadgets.GadgetsHolder;
import net.towee.client.managers.AccountHelper;
import net.towee.client.managers.DashBoardManager;
import net.towee.model.account.Board;
import net.towee.model.account.Gadget;

import com.google.common.base.Preconditions;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.HasOpenHandlers;
import com.google.gwt.event.logical.shared.OpenEvent;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.inject.Inject;

public class DashBoardPresenter implements DashBoard, OpenHandler<Board>, ClickHandler {

	public interface Display extends IsWidget {
		/**
		 * Get container for gadgets in given row and col
		 * 
		 * @param b
		 * @param gadget
		 * @param row
		 * @param cel
		 */
		HasWidgets getGadgetsContainer(Board b, int row, int cel);

		/**
		 * Add board to boards selector
		 * 
		 * @param b
		 */
		void addBoard(Board b);

		/**
		 * Remove board
		 * 
		 * @param board
		 */
		void removeBoard(Board board);

		/**
		 * Show some board
		 * 
		 * @param b
		 */
		void showBoard(Board b);

		/**
		 * @return add gadget button
		 */
		HasClickHandlers getAddGadgetButton();

		/**
		 * @return add board button
		 */
		HasClickHandlers getAddBoardButton();

		/**
		 * @return add board button
		 */
		HasClickHandlers getEditBoardButton();

		/**
		 * @return eventBus for handle showing some board
		 */
		HasOpenHandlers<Board> getBoardOpenContainer();

		/*
		 * Events while board gadgets loading
		 */
		void onBoardLoading(Board b);

		void onBoardLoaded(Board b);

		/**
		 * This method show first board. If no board on dash it show
		 * message "no boards on dash"
		 */
		void showFirstBoard();
	}

	// Injected
	private final Display display;
	private final AccountHelper accountHelper;
	private final DashBoardManager dashboardManager;
	private final HandlerManager eventBus;
	private final ToweeGinjector injector;

	// List of initialized boards
	private final Map<Board, Integer> initializedBoards;
	private Board currentBoard;
	private HasWidgets container;

	@Inject
	public DashBoardPresenter(Display display, AccountHelper accountHelper,
			DashBoardManager dashboardManager, ToweeGinjector injector, HandlerManager eventBus) {
		this.display = display;
		this.accountHelper = accountHelper;
		this.dashboardManager = dashboardManager;
		this.injector = injector;
		this.eventBus = eventBus;

		this.initializedBoards = new HashMap<Board, Integer>();

		// Add handlers
		display.getAddBoardButton().addClickHandler(this);
		display.getAddGadgetButton().addClickHandler(this);
		display.getBoardOpenContainer().addOpenHandler(this);
		display.getEditBoardButton().addClickHandler(this);

		// Add boards
		for (Board b : accountHelper.getAccount().getBoardsContainer().getBoardsList()) {
			display.addBoard(b);
		}

		// Add board handlers
		addHandlers();
	}

	private void addHandlers() {
		eventBus.addHandler(BoardCreatedEvent.TYPE, new BoardCreatedHandler() {
			@Override
			public void onBoardCreated(Board board) {
				// Remove old board
				if(initializedBoards.containsKey(board)) {
					initializedBoards.remove(board);
					display.removeBoard(board);
				}
				
				// Add new board
				display.addBoard(board);
			}
		});

		eventBus.addHandler(BoardRemovedEvent.TYPE, new BoardRemovedHandler() {
			@Override
			public void onBoardRemoved(Board board) {				
				display.removeBoard(board);
				
				if(currentBoard.getId().equals( board.getId() )) {
					if(!accountHelper.getAccount().getBoardsContainer().getBoardsList().isEmpty()) {
						// Get first board
						Board first = accountHelper.getAccount().getBoardsContainer().getBoardsList().get(0);

						// Show board
						showBoard(first);
					}	else 
						currentBoard = null;
				}
			}
		});
		
		eventBus.addHandler(GroupDetachedFromBoardEvent.TYPE, new GroupDetachedFromBoardHandler() {
			@Override
			public void onGroupDetachedFromBoard(String groupId, Board board) {
				// Remove old board
				display.removeBoard(board);
				initializedBoards.remove(board);
				
				// Add new board
				display.addBoard(board);
			}
		});
	}

	@Override
	public void go(HasWidgets container) {
		this.container = container;
		container.clear();
		container.add(display.asWidget());

		// Initialize and show first board in list
		if (initializedBoards.isEmpty()
				&& !accountHelper.getAccount().getBoardsContainer().getBoardsList().isEmpty()) {
			// Get first board
			Board first = accountHelper.getAccount().getBoardsContainer().getBoardsList().get(0);

			// Show board
			showBoard(first);
		}
	}

	/**
	 * Initialize gadgets in given board
	 * 
	 * @param b
	 */
	private void initializeBoard(final Board b) {
		if (!initializedBoards.containsKey(b)) {
			// Start loading
			display.onBoardLoaded(b);

			// Set init flag
			initializedBoards.put(b, 0);

			// Sort gadgets by row
			Collections.sort(b.getGadgets(), new Comparator<Gadget>() {
				@Override
				public int compare(Gadget o1, Gadget o2) {
					return o1.getRow()-o2.getRow();
				}	
			});
			
			// Get and show gadgets
			for (final Gadget g : b.getGadgets()) {
				GadgetsHolder.getGadgetByCanonicalName(g.getGadgetName()).getGadgetContainer(g, b,
						new AsyncCallback<GadgetPresenter>() {
							@Override
							public void onFailure(Throwable caught) {
							}

							@Override
							public void onSuccess(GadgetPresenter result) {
								// Add gadget
								showGadget(b, result, g.getRow(), g.getCel());

								// Increase number of loaded gadgets
								initializedBoards.put(b, initializedBoards.get(b) + 1);

								// Check done event
								if (initializedBoards.get(b) == b.getGadgets().size())
									display.onBoardLoaded(b);
							}
						});
			}
		}
	}

	@Override
	public void showGadget(GadgetPresenter gadget, int row, int cel) {
		Preconditions.checkNotNull(currentBoard, "current board is null");
		showGadget(currentBoard, gadget, row, cel);
	}

	@Override
	public void showGadget(Board b, GadgetPresenter gadget, int row, int cel) {
		gadget.go(display.getGadgetsContainer(b, row, cel));
	}

	/**
	 * Show given board
	 * 
	 * @param b
	 */
	public void showBoard(Board b) {
		// Set current board
		this.currentBoard = b;

		// Initialize this first board
		initializeBoard(b);

		// Show board
		display.showBoard(b);
	}

	/**
	 * On click to add board or gadget
	 */
	@Override
	public void onClick(ClickEvent event) {
		Object source = event.getSource();

		// Add board
		if (source.equals(display.getAddBoardButton())) {
			injector.getBoardEditor().go(container);
		}
		// Add gadget
		else if (source.equals(display.getAddGadgetButton())) {
			Preconditions.checkNotNull(currentBoard, "current board is null");

			// Create gadget pusher
			GadgetPusher gadget = injector.getGadgetPusher();
			gadget.setBoard(currentBoard);
			gadget.go(container);
		}
		// Edit board
		else {
			Preconditions.checkNotNull(currentBoard, "current board is null");

			// Create board editor
			BoardEditor editor = injector.getBoardEditor();
			editor.setBoard(currentBoard);
			editor.go(container);
		}
	}

	/**
	 * On open board event
	 */
	@Override
	public void onOpen(OpenEvent<Board> event) {
		showBoard(event.getTarget());
	}
}
